#pragma once
#include <list>
#include <string>
#include <cassert>
#include <time.h>
#include <cmd/common.h>
#include <cmd/crc.h>

#include "calibrate.hpp"
#include "amc.hpp"

#define AMC_SET(func,x,y)                                              \
        (amc::type(x)==0x7376?amc7376::func(x,y):(                     \
        amc::type(x)==0x7377?amc7377::func(x,y):(                      \
        amc::type(x)==0x7511?amc7511::func(x,y):                       \
        amc::func(x,y))))                                              \

#define AMC(func,x)                                                  \
        (amc::type(x)==0x7376?amc7376::func(x):(                     \
        amc::type(x)==0x7377?amc7377::func(x):(                      \
        amc::type(x)==0x7511?amc7511::func(x):                       \
        amc::func(x))))                                              \

std::vector<unsigned char> Json2Array(Json::Value& root);
namespace capture{
    #pragma pack(1)
    typedef struct __package{
        unsigned short header;
        unsigned short len;
        unsigned char board;
        unsigned char code; 
        unsigned char data[1];
        unsigned short crc;
        unsigned short tail;
    }package;
    #pragma pack()
    unsigned short crc(std::vector<unsigned char>& value){
        if(value.size()-OFFSET_OF(package,board) >= ((package*)&value[0])->len)
            return amc_check_modbus_crc16(&value[OFFSET_OF(package,board)],((package*)&value[0])->len);
        
        assert(false);
        return -1;
    }
    std::vector<unsigned char> make_package(unsigned short len,unsigned char boardid, unsigned char code,std::vector<unsigned char>value){
        package pack_data = { 0x55aa,len };
        std::vector<unsigned char> data(pack_data.len+8);
        memcpy(&data[0],&pack_data,OFFSET_OF(package,board));
        memcpy(&data[OFFSET_OF(package,board)],&boardid,1);
        memcpy(&data[OFFSET_OF(package,board)+1],&code,1);
        if(value.size()>0) memcpy(&data[OFFSET_OF(package,board)+2],&value[0],value.size());
        pack_data.crc = crc(data);
        memcpy(&data[data.size()-4],&pack_data.crc,2);
        pack_data.tail = 0x5a5a;
        memcpy(&data[data.size()-2],&pack_data.tail,2);
        return data;
    }

    std::mutex mw,mw_recv,mw_data;
    std::shared_mutex mtx_cache,mtx_buf;
    std::condition_variable cv,cv_recv_flag,cv_data_flag;
    std::map<boost::asio::ip::tcp::socket*,std::vector<unsigned char>> sockets_buf;
    std::map<boost::asio::ip::tcp::socket*,std::map<int,std::map<int,std::vector<unsigned char>>>> recv_data;
    std::map<boost::asio::ip::tcp::socket*,std::map<unsigned short,std::tuple<std::vector<unsigned char>,bool>>> sockets;
    void recv_thread(std::map<boost::asio::ip::tcp::socket*,std::map<unsigned short,std::tuple<std::vector<unsigned char>,bool>>>::iterator socket)
    {
        std::thread([=]{
            boost::system::error_code ec;
            if(sockets_buf.find(socket->first)==sockets_buf.end()) sockets_buf[socket->first]=std::vector<unsigned char>();
            while(socket!=sockets.end()){
                std::vector<unsigned char> buf(1024*10,0);
                size_t len = socket->first->read_some(boost::asio::buffer(buf), ec);
                if(ec){
                    std::map<boost::asio::ip::tcp::socket*,std::map<unsigned short,std::tuple<std::vector<unsigned char>,bool>>>::iterator del_it;
                    BOOST_LOG_TRIVIAL(info)<<"capture "<< socket->first->remote_endpoint(ec).address().to_string()<<":"<<std::dec
                        <<socket->first->remote_endpoint(ec).port()<<" disconnect";
                    {
                        std::lock_guard<std::mutex> lock(mw);
                        socket->first->close();
                        delete socket->first;
                        del_it = sockets.erase(socket);
                        cv.notify_one();
                    }
                    return;
                }
                if(len<=0) continue;
                std::unique_lock<std::shared_mutex> lock(mtx_buf);
                append(sockets_buf[socket->first],buf,len);
                cv_recv_flag.notify_one();
#if TEST
                BOOST_LOG_TRIVIAL(info)<<"------------capture recv "<<len;
#endif
            }
        }).detach();
        std::thread([=]{//发送线程太快，单独的线程解码
            auto it = socket->first;
            boost::system::error_code ec;
            std::vector<unsigned char> buf;
            while(sockets.find(it)!=sockets.end()){
                size_t len = 0,index=0;
                std::vector<unsigned char> tmp;
                if(buf.size()) buf.swap(tmp);
                {
                    std::unique_lock<std::shared_mutex> lock(mtx_buf);
                    buf.swap(sockets_buf[it]);
                }
                if(tmp.size()) rappend(buf,tmp);
                len = buf.size();
                if(index+4>=len){
                    std::unique_lock<std::mutex> lk(mw_recv);
                    cv_recv_flag.wait(lk);
                }else{
#if TEST
                    BOOST_LOG_TRIVIAL(info)<<"------------capture process "<<buf.size();
#endif
                }
                while(index+4<len){
                    if (0xAA==buf[index]&&0x55==buf[index+1]) {
                        int pack_len = buf[index+2]|(buf[index+3]<<8);
                        if(index+pack_len+8<=len){
                            if(buf[index+pack_len+6]==0x5A&&buf[index+pack_len+7]==0x5A){
                                int crc = buf[index+pack_len+4]|(buf[index+pack_len+5]<<8);
                                if(crc != amc_check_modbus_crc16(&buf[index+4],pack_len)){
                                    BOOST_LOG_TRIVIAL(warning)<<"crc check error";
                                }else{
                                    std::vector<unsigned char> data(buf.begin()+index+6,buf.begin()+(index+6+pack_len-2));
                                    {
                                        std::unique_lock<std::shared_mutex> lock(mtx_cache);
                                        recv_data[it][buf[index+4]][buf[index+5]]=data.size()==0?std::vector<unsigned char>(1,0):data;
                                    }
                                    cv_data_flag.notify_one();
                                    if(buf[index+5]==0x10&&data.size()>0){
                                        BOOST_LOG_TRIVIAL(info)<<"capture found board=="<<(int)buf[index+4]<<":0x"<<std::hex<<amc::type(data);
                                        std::get<0>(capture::sockets[it][buf[index+4]])=data;
                                        int card_id = amc::type(data);
                                        int board_id = buf[index+4];
                                        boost::asio::post(pool, std::bind([card_id,board_id]{
                                            //读取校正配置文件
                                            std::stringstream ss;
                                            ss<<std::hex<<"0x"<<card_id;
                                            static std::map<int,int> card_mp;
                                            card_mp[board_id] = card_id;
                                            capture::read_config(card_id,board_id,
                                                std::count_if(card_mp.begin(),card_mp.end(),[&](const auto& p){return p.second==card_id;}),
                                                ss.str()+".csv");
                                        }));
                                    }
                                }
                                index += pack_len + 8;
                            }else{
                                index++;
                                BOOST_LOG_TRIVIAL(warning)<<"capture frame header error.";
                            }
                        }else{
                            break;
                        }
                    }else{
                        index++;
                        BOOST_LOG_TRIVIAL(warning)<<"capture frame tail error.";
                    }
                }
                if(index >0) {
                    std::vector<unsigned char> left(buf.begin()+index,buf.end());
                    buf.swap(left);
                }
#if TEST
                if(buf.size()) BOOST_LOG_TRIVIAL(info)<<"------------capture process "<<buf.size();
#endif
            }
            sockets_buf.erase(it);
        }).detach();
    }
    void recv(std::map<boost::asio::ip::tcp::socket*,std::map<unsigned short,std::tuple<std::vector<unsigned char>,bool>>>::iterator socket,
        int board,int code, bool write,std::function<void(int,int,const std::vector<unsigned char>&)> callback){
        auto it = socket->first;
        std::vector<unsigned char> data;
        auto end = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(WAITFOR_DATA);
        while(std::chrono::high_resolution_clock::now()<end){
            {
                std::unique_lock<std::shared_mutex> lock(mtx_cache);
                if((code==0x20||(!write&&code==0x10))&&recv_data[it][board][code].size()){
                    data = recv_data[it][board][code];
                    recv_data[it][board][code].clear();
                    break;
                }else if(code!=0x20&&recv_data[it][0x80][code].size()){
                    data = recv_data[it][0x80][code];
                    recv_data[it][0x80][code].clear();
                    board = 0x80;
                    break;  
                }else if(code!=0x20&&recv_data[it][0x81][code].size()){
                    data = recv_data[it][0x81][code];
                    recv_data[it][0x81][code].clear();
                    board = 0x81;
                    break;
                }
            }
            std::unique_lock<std::mutex> lk(mw_data);
            cv_data_flag.wait_for(lk,std::chrono::milliseconds(2));
        }
        if (data.size()) {
            callback(board,code,data);
        }else{
            callback(board,code,data);
            BOOST_LOG_TRIVIAL(info)<<"capture "<<board<<" code not found "<<std::hex<<code;
        }
    }
    void init(){
        app().registerHandler("/device_start",[&](const HttpRequestPtr &req,std::function<void(const HttpResponsePtr &)> &&callback) {
            auto resp = HttpResponse::newHttpResponse();
            unsigned int board_id = atoi(req->getParameter("id").c_str());
            if(capture::sockets.size()==0||capture::sockets.begin()->second.find(board_id)==capture::sockets.begin()->second.end()) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            int start_flag = 0x1<<board_id;
            boost::system::error_code ignored_error;
            for(auto it:capture::sockets.begin()->second) if(it.first!=board_id) start_flag|=(std::get<1>(it.second)?0x1:0x0)<<it.first;
            std::vector<unsigned char> data = capture::make_package(6,0xff,0x30,data2uchar(start_flag));
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            capture::recv(capture::sockets.begin(),board_id,0x30,true,[&](int board,int code,const std::vector<unsigned char>& data){
                resp->setBody(std::string("{\"state\":"+std::to_string(board==0x80?0:1)+"}"));
                callback(resp);
                if(capture::sockets.begin()!=capture::sockets.end()&&!std::get<1>(capture::sockets.begin()->second[board_id])){
                    std::get<1>(capture::sockets.begin()->second[board_id]) = true;
                    boost::asio::post(pool, std::bind([=]{
                        try{
                            std::map<int,std::vector<double>> cache_data;
                            int feature_interval = settings.get<int>("feature.interval", 10);
                            settings.put("feature.interval",feature_interval);
                            int save_interval = settings.get<int>("save.interval", 1);
                            settings.put("save.interval",save_interval);
                            int max_size = settings.get<int>("save.max_size", 10);
                            settings.put("save.max_size",max_size);
                            std::string save_path = settings.get<std::string>("save.path", "./raw");
                            settings.put("save.path",save_path);
                            std::filesystem::create_directories(save_path);
                            init_binary_log(save_path+"/capture_"+std::to_string(board_id)+".bin",save_interval,max_size);
                            std::string check_point_id = settings.get<std::string>("feature.check_point_id", "default");
                            settings.put("feature.check_point_id",check_point_id);
                            std::string check_point_name = settings.get<std::string>("feature.interval", "default");
                            settings.put("feature.interval",check_point_name);
                            auto feature_time=std::chrono::high_resolution_clock::now()+std::chrono::seconds(feature_interval);
                            auto save_time=std::chrono::high_resolution_clock::now()+std::chrono::seconds(save_interval);
                            auto last_time=std::chrono::high_resolution_clock::now()+std::chrono::milliseconds(WEBSOCKET_INTERVAL);
                            while(std::get<1>(capture::sockets.begin()->second[board_id])){
                                capture::recv(capture::sockets.begin(),board_id,0x20,true,[&](int board,int code,const std::vector<unsigned char>& data){
                                    for(auto itt:websoket_connection){
                                        if(data.size() && std::chrono::high_resolution_clock::now()>last_time){
                                            std::vector<unsigned char> send_data;
                                            append(send_data,data2uchar((unsigned char)board));
                                            switch(amc::type(std::get<0>(capture::sockets.begin()->second[board]))){
                                                case 0x7376:{
                                                    append(send_data,data2uchar((unsigned char)6));
                                                    append(send_data,data2uchar("float",6));
                                                    append(send_data,data2uchar((int)(data.size()/4/6)));
                                                    break;
                                                }case 0x7377:{
                                                    append(send_data,data2uchar((unsigned char)16));
                                                    append(send_data,data2uchar("float",6));
                                                    append(send_data,data2uchar((int)(data.size()/4/16)));
                                                    break;
                                                }case 0x7511:{
                                                    append(send_data,data2uchar((unsigned char)32));
                                                    append(send_data,data2uchar("bit",6));
                                                    append(send_data,data2uchar((int)(data.size()/4)));
                                                    break;
                                                }default:continue;
                                            }
                                            append(send_data,data);
                                            itt->send((char*)&send_data[0],send_data.size(),WebSocketMessageType::Binary);
                                            if(++std::find(websoket_connection.begin(),websoket_connection.end(),itt)==websoket_connection.end())
                                                last_time = std::chrono::high_resolution_clock::now()+std::chrono::milliseconds(WEBSOCKET_INTERVAL);
                                        }
                                    }
                                    //数据校正
                                    try{
                                        feature(data, amc::type(std::get<0>(capture::sockets.begin()->second[board])), board, cache_data);
                                        if(std::chrono::high_resolution_clock::now()>feature_time && data.size()){
                                            send_feature_data(amc::type(std::get<0>(capture::sockets.begin()->second[board])),board,cache_data,check_point_id,check_point_name);
                                            feature_time=std::chrono::high_resolution_clock::now()+std::chrono::seconds(feature_interval);
                                        }

                                        //数据保存
                                        if(data.size()) {
                                            BOOST_LOG_TRIVIAL(trace)<<std::string((char*)&data[0],data.size());
                                        }
                                    }catch(...){
                                        BOOST_LOG_TRIVIAL(error)<<"feature fail.";
                                    }
                                });
                            }
                            BOOST_LOG_TRIVIAL(info)<<"capture "<<std::hex<<board_id<<" task finish ";
                        }catch(...){
                            BOOST_LOG_TRIVIAL(error)<<"caputure "<<std::hex<<board_id<<" task fail.";
                        }
                    }));
                }
            });
        },{Get})
        .registerHandler("/device_stop",[&](const HttpRequestPtr &req,std::function<void(const HttpResponsePtr &)> &&callback) {
            auto resp = HttpResponse::newHttpResponse();
            unsigned int board_id = atoi(req->getParameter("id").c_str());
            if(capture::sockets.size()==0) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            int stop_flag = 0x0<<board_id;
            boost::system::error_code ignored_error;
            for(auto it:capture::sockets.begin()->second) if(it.first!=board_id) stop_flag|=(std::get<1>(it.second)?0x1:0x0)<<it.first;
            std::vector<unsigned char> data = capture::make_package(6,0xff,0x30,data2uchar(stop_flag));
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            capture::recv(capture::sockets.begin(),board_id,0x30,true,[&](int board,int code,const std::vector<unsigned char>& data){
                resp->setBody(R"({"state":0})");
                callback(resp);
            });
            std::get<1>(capture::sockets.begin()->second[board_id]) = false;
        },{Get})
        .registerHandler("/device_restart",[&](const HttpRequestPtr &req,std::function<void(const HttpResponsePtr &)> &&callback) {
            auto resp = HttpResponse::newHttpResponse();
            unsigned int board_id = atoi(req->getParameter("id").c_str());
            if(capture::sockets.size()==0||capture::sockets.begin()->second.find(board_id)==capture::sockets.begin()->second.end()) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            boost::system::error_code ignored_error;
            std::vector<unsigned char> data = capture::make_package(6,0xff,0x30,{0});
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            data = capture::make_package(6,0xff,0x30,data2uchar(0x1<<atoi(req->getParameter("id").c_str())));
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            resp->setBody(R"({"state":0})");
            callback(resp);
        },{Get})
        .registerHandler("/device_query",[&](const HttpRequestPtr &req,std::function<void(const HttpResponsePtr &)> &&callback) {
            auto resp = HttpResponse::newHttpResponse();
            unsigned int board_id = atoi(req->getParameter("id").c_str()), 
                board_address = atoi(req->getParameter("address").c_str());
            if(capture::sockets.size()==0||capture::sockets.begin()->second.find(board_id)==capture::sockets.begin()->second.end()) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            boost::system::error_code ignored_error;
            std::vector<unsigned char> data = capture::make_package(2,0xff,0x10,{});
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            capture::recv(capture::sockets.begin(),board_id,0x10,false,[&](int board,int code,const std::vector<unsigned char>& data){
                if(code ==0x10 && data.size()>0) std::get<0>(capture::sockets.begin()->second[board])=data;
            });
            if(capture::sockets.size()==0) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            Json::Value json_ret;
            Json::FastWriter writer;
            json_ret["state"]=0;
            auto& config=capture::sockets.begin()->second.find(board_id)->second;
            switch(board_address){
                case 1:{
                    auto item = AMC(channel_enable,std::get<0>(config));
                    for(auto it: item)
                        json_ret["data"].append(it);
                    break;
                }case 2:{
                    json_ret["data"]=AMC(sampling,std::get<0>(config));
                    break;
                }case 3:{
                    auto item = AMC(channel_mode,std::get<0>(config));
                    for(auto it: item) json_ret["data"].append(it);
                    break;
                }case 4:{
                    json_ret["data"]=AMC(trigger,std::get<0>(config));
                    break;
                }case 5:{
                    auto item = AMC(gain,std::get<0>(config));
                    for(auto it: item) json_ret["data"].append(it);
                    break;
                }case 6:{
                    auto item = AMC(coupling,std::get<0>(config));
                    for(auto it: item) json_ret["data"].append(it);
                    break;
                }case 7:{
                    json_ret["data"]=AMC(samplen,std::get<0>(config));
                    break;
                }case 8:{
                    json_ret["data"]=AMC(time,std::get<0>(config));
                    break;
                }case 9:{
                    json_ret["data"]=AMC(filter,std::get<0>(config));
                    break;
                }default:break;
            }
            resp->setBody(writer.write(json_ret));
            callback(resp);
        },{Get})
        .registerHandler("/device_setting",[&](const HttpRequestPtr &req,std::function<void(const HttpResponsePtr &)> &&callback) {
            auto resp = HttpResponse::newHttpResponse();
            unsigned int board_id = atoi(req->getParameter("id").c_str()),
                board_address = atoi(req->getParameter("address").c_str());
            Json::Reader reader;
            Json::Value json_get;
            if(capture::sockets.size()==0||capture::sockets.begin()->second.find(board_id)==capture::sockets.begin()->second.end()) {
                resp->setBody(R"({"state":1})");
                callback(resp);
                return;
            }
            auto& config=capture::sockets.begin()->second.find(board_id)->second;
            switch(board_address){
                case 1:{
                    if(reader.parse(req->getParameter("value"), json_get)){
                        auto item = Json2Array(json_get);
                        AMC_SET(channel_enable,std::get<0>(config),item);
                    }
                    break;
                }
                case 2:{
                    AMC_SET(sampling,std::get<0>(config),atoi(req->getParameter("value").c_str()));
                    break;
                }case 3:{
                    if(reader.parse(req->getParameter("value"), json_get)){
                        auto item = Json2Array(json_get);
                        AMC_SET(channel_mode,std::get<0>(config),item);
                    }
                    break;
                }
                case 4:{
                    AMC_SET(trigger,std::get<0>(config),atoi(req->getParameter("value").c_str()));
                    break;
                }case 5:{
                    if(reader.parse(req->getParameter("value"), json_get)){
                        auto item = Json2Array(json_get);
                        AMC_SET(gain,std::get<0>(config),item);
                    }
                    break;
                }
                case 6:{
                    if(reader.parse(req->getParameter("value"), json_get)){
                        auto item = Json2Array(json_get);
                        AMC_SET(coupling,std::get<0>(config),item);
                    }
                    break;
                }
                case 7:{
                    AMC_SET(samplen,std::get<0>(config),atoi(req->getParameter("value").c_str()));
                    break;
                }case 8:{
                    AMC_SET(time,std::get<0>(config),atoi(req->getParameter("value").c_str()));
                    break;
                }case 9:{
                    AMC_SET(filter,std::get<0>(config),atoi(req->getParameter("value").c_str()));
                    break;
                }
                default:break;
            }
            boost::system::error_code ignored_error;
            std::vector<unsigned char> data = capture::make_package(0x42,board_id,0x10,std::get<0>(config));
            capture::sockets.begin()->first->write_some(boost::asio::buffer(data),ignored_error);
            capture::recv(capture::sockets.begin(),board_id,0x10,true,[&](int board,int code,const std::vector<unsigned char>& data){
                resp->setBody(std::string("{\"state\":"+std::to_string(board==0x80?0:1)+"}"));
                callback(resp);
            });
        },{Get});
    }
}